<template>
	<div :id="idName" @click="generate">
		<slot> Download {{ name }} </slot>
	</div>
</template>

<script>
	import download from "./download";

	export default {
		name: "downloadExcel",
		props: {
			// mime type [xls, csv]
			type: {
				type: String,
				default: "xls",
			},
			// Json to download
			data: {
				type: Array,
				required: false,
				default: null,
			},
			// fields inside the Json Object that you want to export
			// if no given, all the properties in the Json are exported
			fields: {
				type: Object,
				default: () => null,
			},
			// this prop is used to fix the problem with other components that use the
			// variable fields, like vee-validate. exportFields works exactly like fields
			exportFields: {
				type: Object,
				default: () => null,
			},
			// Use as fallback when the row has no field values
			defaultValue: {
				type: String,
				required: false,
				default: "",
			},
			// Title(s) for the data, could be a string or an array of strings (multiple titles)
			header: {
				default: null,
			},
			// Footer(s) for the data, could be a string or an array of strings (multiple footers)
			footer: {
				default: null,
			},
			// filename to export
			name: {
				type: String,
				default: "data.xls",
			},
			fetch: {
				type: Function,
			},
			meta: {
				type: Array,
				default: () => [],
			},
			worksheet: {
				type: String,
				default: "Sheet1",
			},
			//event before generate was called
			beforeGenerate: {
				type: Function,
			},
			//event before download pops up
			beforeFinish: {
				type: Function,
			},
			// Determine if CSV Data should be escaped
			escapeCsv: {
				type: Boolean,
				default: true,
			},
			// long number stringify
			stringifyLongNum: {
				type: Boolean,
				default: false,
			},
		},
		computed: {
			// unique identifier
			idName() {
				var now = new Date().getTime();
				return "export_" + now;
			},

			downloadFields() {
				if (this.fields) return this.fields;

				if (this.exportFields) return this.exportFields;
			},
		},
		methods: {
			async generate() {
				if (typeof this.beforeGenerate === "function") {
					await this.beforeGenerate();
				}
				let data = this.data;
				if (typeof this.fetch === "function" || !data) data = await this.fetch();

				if (!data || !data.length) {
					return;
				}

				let json = this.getProcessedJson(data, this.downloadFields);
				if (this.type === "html") {
					// this is mainly for testing
					return this.export(
						this.jsonToXLS(json),
						this.name.replace(".xls", ".html"),
						"text/html"
					);
				} else if (this.type === "csv") {
					return this.export(
						this.jsonToCSV(json),
						this.name.replace(".xls", ".csv"),
						"application/csv"
					);
				}
				return this.export(
					this.jsonToXLS(json),
					this.name,
					"application/vnd.ms-excel"
				);
			},
			/*
		Use downloadjs to generate the download link
		*/
			export: async function(data, filename, mime) {
				let blob = this.base64ToBlob(data, mime);
				if (typeof this.beforeFinish === "function") await this.beforeFinish();
				download(blob, filename, mime);
			},
			/*
		jsonToXLS
		---------------
		Transform json data into an xml document with MS Excel format, sadly
		it shows a prompt when it opens, that is a default behavior for
		Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
		*/
			jsonToXLS(data) {
				let xlsTemp =
					'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
				let xlsData = "<thead>";
				const colspan = Object.keys(data[0]).length;
				let _self = this;

				//Header
				const header = this.header || this.$attrs.title;
				if (header) {
					xlsData += this.parseExtraData(
						header,
						'<tr><th colspan="' + colspan + '">${data}</th></tr>'
					);
				}

				//Fields
				xlsData += "<tr>";
				for (let key in data[0]) {
					xlsData += "<th>" + key + "</th>";
				}
				xlsData += "</tr>";
				xlsData += "</thead>";

				//Data
				xlsData += "<tbody>";
				data.map(function(item, index) {
					xlsData += "<tr>";
					for (let key in item) {
						// 对于大数字，使用特殊格式确保Excel将其作为文本处理
						const value = item[key];
						const formattedValue = typeof value === 'string' && /^\d{15,}$/.test(value) ?
							`'${value}` // 添加单引号前缀，强制Excel将其作为文本处理
							:
							_self.preprocessLongNum(_self.valueReformattedForMultilines(value));

						xlsData += "<td>" + formattedValue + "</td>";
					}
					xlsData += "</tr>";
				});
				xlsData += "</tbody>";

				//Footer
				if (this.footer != null) {
					xlsData += "<tfoot>";
					xlsData += this.parseExtraData(
						this.footer,
						'<tr><td colspan="' + colspan + '">${data}</td></tr>'
					);
					xlsData += "</tfoot>";
				}

				return xlsTemp
					.replace("${table}", xlsData)
					.replace("${worksheet}", this.worksheet);
			},
			/*
		jsonToCSV
		---------------
		Transform json data into an CSV file.
		*/
			jsonToCSV(data) {
				let _self = this;
				var csvData = [];

				//Header
				const header = this.header || this.$attrs.title;
				if (header) {
					csvData.push(this.parseExtraData(header, "${data}\r\n"));
				}

				//Fields
				for (let key in data[0]) {
					csvData.push(key);
					csvData.push(",");
				}
				csvData.pop();
				csvData.push("\r\n");
				//Data
				data.map(function(item) {
					for (let key in item) {
						let escapedCSV = item[key] + "";
						// 确保大数字被当作文本处理
						if (_self.escapeCsv) {
							escapedCSV = '="' + escapedCSV + '"'; // 强制Excel将其作为文本处理
							if (escapedCSV.match(/[,"\n]/)) {
								escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
							}
						}
						csvData.push(escapedCSV);
						csvData.push(",");
					}
					csvData.pop();
					csvData.push("\r\n");
				});
				//Footer
				if (this.footer != null) {
					csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
				}
				return csvData.join("");
			},
			/*
		getProcessedJson
		---------------
		Get only the data to export, if no fields are set return all the data
		*/
			getProcessedJson(data, header) {
				let keys = this.getKeys(data, header);
				let newData = [];
				let _self = this;
				data.map(function(item, index) {
					let newItem = {};
					for (let label in keys) {
						let property = keys[label];
						newItem[label] = _self.getValue(property, item);
					}
					newData.push(newItem);
				});

				return newData;
			},

			// 将时间戳转换为日期格式
			timestampToTime(timestamp) {
				let date = new Date(timestamp);
				let Y = date.getFullYear() + '-';
				let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
				let D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
				let h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
				let m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
				let s = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds());
				return Y + M + D + h + m + s;
			},
			getKeys(data, header) {
				if (header) {
					return header;
				}

				let keys = {};
				for (let key in data[0]) {
					keys[key] = key;
				}
				return keys;
			},
			/*
		parseExtraData
		---------------
		Parse title and footer attribute to the csv format
		*/
			parseExtraData(extraData, format) {
				let parseData = "";
				if (Array.isArray(extraData)) {
					for (var i = 0; i < extraData.length; i++) {
						if (extraData[i])
							parseData += format.replace("${data}", extraData[i]);
					}
				} else {
					parseData += format.replace("${data}", extraData);
				}
				return parseData;
			},

			// 添加订单状态转换方法
			getOrderStatus(status) {
				const statusMap = {
					'-1': '已关闭',
					'0': '未支付',
					'1': '已支付',
					'2': '已部分退款',
					'3': '已全额退款',
					'4': '已发货',
					'5': '待评价',
					'6': '退款中',
					'7': '已完成'
				};
				return statusMap[status] || status;
			},

			// 添加金额转换方法
			formatMoney(amount) {
				if (amount === undefined || amount === null) return '';
				return (amount / 100).toFixed(2) + '元';
			},

			getValue(key, item) {
				const field = typeof key !== "object" ? key : key.field;
				let value = this.defaultValue;

				if (!field) {
					value = item;
				} else {
					// 处理数组访问，如 "goods_arr[0].goods_id" 或 "goods_arr.*.goods_id"
					const arrayMatch = field.match(/^(.*?)(?:\[(\d+)\]|\.\*)(.*)$/);

					if (arrayMatch) {
						const [_, arrayField, index, remaining] = arrayMatch;
						const array = item[arrayField];

						if (Array.isArray(array)) {
							if (index !== undefined) {
								// 访问特定索引
								if (array[index]) {
									if (remaining) {
										const nestedField = remaining.startsWith('.') ? remaining.slice(1) : remaining;
										value = this.getValueFromNestedItem(array[index], nestedField.split('.'));
									} else {
										value = array[index];
									}
								}
							} else {
								// 访问所有元素
								const nestedField = remaining.startsWith('.') ? remaining.slice(1) : remaining;
								value = array.map((item, idx) => {
									let itemValue = nestedField ?
										this.getValueFromNestedItem(item, nestedField.split('.')) :
										item;
									return `[${idx + 1}] ${itemValue}`;
								}).join('\n');
							}
						}
					} else {
						// 原有的字段访问逻辑
						let indexes = typeof field !== "string" ? [] : field.split(".");
						if (indexes.length > 1) {
							value = this.getValueFromNestedItem(item, indexes);
						} else {
							value = this.parseValue(item[field]);
						}
					}
				}

				if (key.hasOwnProperty("callback")) {
					value = this.getValueFromCallback(value, key.callback);
				}

				// 处理订单号等大数字字段，需要在处理金额和时间之前
				const bigNumberFields = ['order_no', 'transaction_id', 'out_trade_no'];
				const fieldName = typeof field === 'string' ? field.split('.').pop() : '';
				if (bigNumberFields.includes(fieldName) && value !== undefined) {
					value = `${value}`; // 转换为字符串
				}

				// 自动处理时间字段
				const timeFields = ['pay_date', 'create_time', 'update_time', 'time', 'date'];
				if (timeFields.includes(fieldName) && value) {
					value = this.timestampToTime(value);
				}

				// 处理订单状态字段
				if (fieldName === 'status' && value !== undefined) {
					value = this.getOrderStatus(value);
				}

				// 处理金额字段
				const moneyFields = ['total_fee', 'pay_fee', 'refund_fee', 'preferential'];
				if (moneyFields.includes(fieldName) && value !== undefined) {
					value = this.formatMoney(value);
				}

				return value;
			},

			/*
			convert values with newline \n characters into <br/>
			*/
			valueReformattedForMultilines(value) {
				if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
				else return value;
			},
			preprocessLongNum(value) {
				if (this.stringifyLongNum) {
					if (String(value).startsWith("0x")) {
						return value;
					}
					if (!isNaN(value) && value != "") {
						if (value > 99999999999 || value < 0.0000000000001) {
							return '="' + value + '"';
						}
					}
				}
				return value;
			},
			getValueFromNestedItem(item, indexes) {
				let nestedItem = item;
				for (let index of indexes) {
					if (nestedItem) nestedItem = nestedItem[index];
				}
				return this.parseValue(nestedItem);
			},

			getValueFromCallback(item, callback) {
				if (typeof callback !== "function") return this.defaultValue;
				const value = callback(item);
				return this.parseValue(value);
			},
			parseValue(value) {
				return value || value === 0 || typeof value === "boolean" ?
					value :
					this.defaultValue;
			},
			base64ToBlob(data, mime) {
				let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
				let bstr = atob(base64);
				let n = bstr.length;
				let u8arr = new Uint8ClampedArray(n);
				while (n--) {
					u8arr[n] = bstr.charCodeAt(n);
				}
				return new Blob([u8arr], {
					type: mime
				});
			},
		}, // end methods
	};
</script>